package ludowork.components;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.FocusEvent;
import java.awt.image.BufferedImage;
import java.util.Properties;
import javax.swing.JPanel;
import ludowork.core.ImageSource;
import ludowork.core.ImageStore;

public class Visor extends JPanel implements ImageStore {

    public static final int BASE_RECT = 0;
    public static final int FOCUSED_RECT = 1;

    public Visor() {
        imageSource = null;
        inImage = null;
        activeImage = 0;
        ratio = 1.0;
        increment = 1.1;
        xOrigin = 0;
        yOrigin = 0;
        xFOrigin = 0;
        yFOrigin = 0;
    }
    

    @Override
    public void setImageSource(ImageSource imageSource) {
        if(imageSource!=null){
            this.imageSource = imageSource;
            activeImage = 0;
            resetImageStatus();
        }
        
    }

    @Override
    public boolean setActiveImage(int i) {
        if(i<imageSource.getSize() && i>=0){
            activeImage = i;
            refreshImage();
            return true;
        } else {
            return false;
        }
    }

    @Override
    public int getActiveImageId() {
        return activeImage;
    }

    @Override
    public boolean nextImage() {
        return setActiveImage(activeImage + 1);
    }

    @Override
    public boolean prevImage() {
        return setActiveImage(activeImage - 1);
    }

    @Override
    public void updateImage(BufferedImage image) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    
    @Override
    public BufferedImage getActiveImage(){
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void stepBackImage(int index) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void reset() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void setMaxHistory(int size) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public int getMaxHistory() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void eraseHistory() {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    
    @Override
    public Properties getImageInfo() {
        Properties  p = new Properties();
        
        return p;
    }



    /**
     * Set increment for zoom ratio.
     *
     * @param increment
     */
    public void setRatioIncrement(double increment) {
        this.increment = increment;
    }

    /**
     * Returns actual zoom ratio
     *
     * @return actual zoom ratio
     */
    public double getRatioIncrement() {
        return this.increment;
    }

    /**
     * set zoom ratio as 1
     */
    public void setRatioOne() {
        this.ratio = 1.0;
    }

    /**
     * increment zoom
     */
    public void incrementRatio() {
        this.ratio = ratio * increment;
        if (inImage == null) {
            return;
        }
        calculateSize();
    }

    /**
     * decrease zoom
     */
    public void decrementRatio() {
        this.ratio = ratio / increment;
        if (inImage == null) {
            return;
        }
        calculateSize();
    }

    /**
     * Increase zoom from given component position
     *
     * @param x x component coord
     * @param y y component coord
     */
    public void incrementRatio(int x, int y) {
        this.ratio = ratio * increment;
        if (inImage == null) {
            return;
        }
        calculateSize(x, y);
    }

    /**
     * Decrease zoom from given component position
     *
     * @param x x component coord
     * @param y y component coord
     */
    public void decrementRatio(int x, int y) {
        this.ratio = ratio / increment;
        if (inImage == null) {
            return;
        }
        calculateSize(x, y);
    }

    /**
     * Displaces image origin.
     *
     * @param xShift x displacement
     * @param yShift y displacement
     */
    public void shiftOrigin(int xShift, int yShift) {
        xOrigin = xFOrigin + xShift;
        yOrigin = yFOrigin + yShift;
    }

    /**
     * Used when origin displacement ends.
     */
    public void fixOrigin() {
        xFOrigin = xOrigin;
        yFOrigin = yOrigin;
    }

    /**
     * Put image origin on component origin
     */
    public void resetOrigin() {
        xOrigin = 0;
        yOrigin = 0;
    }

    /**
     * set visor image
     *
     * @param Img source image
     */
    private void setImg(BufferedImage Img) {
        inImage = Img;
        if (inImage == null) {
            return;
        }
        calculateSize();
        this.repaint();
    }

    /**
     * Returns absolute image x coord form component relative x coord
     *
     * @param x component x coord
     * @return image absolute x coord
     */
    public int absoluteX(int x) {
        if (inImage == null) {
            return -1;
        }
        int ax = (int) ((x - xOrigin) / ratio);
        if (ax > inImage.getWidth()) {
            ax = inImage.getWidth();
        } else if (ax < 0) {
            ax = 0;
        }
        return ax;
    }

    /**
     * Returns absolute image y coord form component relative y coord
     *
     * @param y component y coord
     * @return image absolute y coord
     */
    public int absoluteY(int y) {
        if (inImage == null) {
            return -1;
        }
        int ay = (int) ((y - yOrigin) / ratio);
        if (ay > inImage.getHeight()) {
            ay = inImage.getHeight();
        } else if (ay < 0) {
            ay = 0;
        }
        return ay;
    }

    /**
     * calculate the image show size
     */
    private void calculateSize() {
        drawWidth = (int) (((double) inImage.getWidth()) * ratio);
        drawHeight = (int) (((double) inImage.getHeight()) * ratio);
    }

    /**
     * calculate the image show size. Keep x-y component point where it is.
     *
     * @param x component x coord
     * @param y component y coord
     */
    private void calculateSize(int x, int y) {
        int xoxc = x - xOrigin;
        int yoyc = y - yOrigin;
        double xoxcRatio = xoxc / (double) drawWidth;
        double yoycRatio = yoyc / (double) drawHeight;
        drawWidth = (int) (((double) inImage.getWidth()) * ratio);
        drawHeight = (int) (((double) inImage.getHeight()) * ratio);
        xOrigin = (int) (x - xoxcRatio * drawWidth);
        yOrigin = (int) (y - yoycRatio * drawHeight);
        avoidImageLoss();
    }

    /**
     * Avoid image to get out of view.
     */
    private void avoidImageLoss() {
        int xMargin = (int) (this.getWidth() * margin);
        int yMargin = (int) (this.getHeight() * margin);
        if (xOrigin > this.getWidth() - xMargin) {
            xOrigin = this.getWidth() - xMargin;
        }
        if (yOrigin > this.getHeight() - yMargin) {
            yOrigin = this.getHeight() - yMargin;
        }
        if ((xOrigin < 0) && ((xOrigin + drawWidth) < xMargin)) {
            xOrigin = xMargin - drawWidth;
        }
        if ((yOrigin < 0) && (yOrigin + drawHeight) < yMargin) {
            yOrigin = yMargin - drawHeight;
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        buildOut(g);
    }

    /**
     * paint image and zones
     *
     * @param g
     */
    private void buildOut(Graphics g) {
        if (inImage == null) {
            return;
        }
        avoidImageLoss();
        if (inImage != null) {
            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2.drawImage(inImage, xOrigin, yOrigin, drawWidth, drawHeight, null);
        }
    }
    
    
    private void refreshImage() {
        setImg(imageSource.getImage(activeImage));
        //this.processComponentEvent(new FocusEvent(this, FocusEvent.FOCUS_GAINED));
    }
    
    private void resetImageStatus() {
        resetOrigin();
        setRatioOne();
        refreshImage();
    }
    
    
    private ImageSource imageSource;
    private BufferedImage inImage;
    private int activeImage;
    private double ratio;
    private double increment;
    private int drawWidth;
    private int drawHeight;
    private int xOrigin;
    private int yOrigin;
    private int xFOrigin;
    private int yFOrigin;
    private double margin = 0.10;

}
